﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Security.Cryptography;
using System.IO.MemoryMappedFiles;


namespace vodd
{
    public struct dlInfo
    {
        public uint pid;
        public uint sid;
        public uint did;
        public uint pfg;
        public String md5hash;
        public uint cid;
        public uint usn;
        public String time;
        public String videoname;    //从服务器返回包中获取的视频名称
        public String fileext;      //从服务器返回包中获取的文件拓展名
        public Int64 filelen;      //文件总长度
        public Int64 filepos;      //当前文件下载位置
    }
    
    public delegate void ProcessDelegate(object sender, EventArgs e);

    public class MediaInfoParser
    {
        public Boolean Err;
        public string ErrStr;
        public uint cid;
        public uint usn;
        public Int64 filelen;
        public string time;
        public string videoname;
        public string fileext;
        public MediaInfoParser(byte[] info)
        {
            byte[] mgc_bytes = { 0x42,0x4d,0x54,0x50,0x68};
            int len;
            int j;
            int i;
            Err = false;
            len = info.Length;
            if (len!= 332)
            {
                Err = true;
                ErrStr = "数据长度错误！";
                return;
            }
            i=15;
            while(i>11)
            {
                cid<<=8;
                cid+=info[i];
                i--;
            }
            i = 19;
            while(i>15)
            {
                usn<<=8;
                usn+=info[i];
                i--;
            }
            i=31;
            while(i>23)
            {
                filelen<<=8;
                filelen+=info[i];
                i--;
            }
            time = Encoding.GetEncoding("gb2312").GetString(info, 52, 20).TrimEnd('\0'); ;
            videoname =Encoding.GetEncoding("gb2312").GetString(info, 80, 80).TrimEnd('\0');
            fileext = Encoding.GetEncoding("gb2312").GetString(info, 160, 8).Trim('\0');
        }
    }

    public class constructor
    {
        private string md5hash(string input)
        {
            MD5 md5Hasher = MD5.Create();
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();
        }

        public string mgc_hash(uint pid, uint sid, uint did, uint pfg)
        {
            string mgc_str;
            mgc_str=String.Format("boful+pid={0};sid={1};did={2};fil=;pfg={3};truran",pid,sid,did,pfg);
            return md5hash(mgc_str);
        }

        public string CommReqInfo(dlInfo dl)
        {
            StringBuilder strTemp = new StringBuilder();
            
            strTemp.Append("GET / HTTP/1.0\n");
            strTemp.Append("Accept: */*\n");
            strTemp.Append("User-Agent: BMTPAgent\n");
            strTemp.Append("Host: VOD\n");
            strTemp.Append("UGT:NETPLAYER\n");
            strTemp.Append("WAY:1\n");
            strTemp.Append("MTD:1\n");
            strTemp.Append("FPT:1\n");
            strTemp.Append("CID:4294967295\n");
            strTemp.Append("USN:0\n");
            strTemp.Append("STL:2\n");
            strTemp.Append("DFG:0\n");
            strTemp.Append("DPT:0\n");
            strTemp.Append("AFG:basic\n");
            strTemp.Append("URN:anonymity\n");
            strTemp.Append("PWD:vod\n");
            strTemp.Append("OS:1\n");
            strTemp.Append("ULG:2052\n");
            strTemp.Append("HST:VODClient\n");
            strTemp.Append("UIP:192.168.1.1\n");
            strTemp.Append("MAC:00-00-00-22-11-00\n");
            strTemp.Append("GW:0.0.0.0\n");
            strTemp.AppendFormat("PFG:{0}\n",dl.pfg);
            strTemp.Append("FIL:\n");
            strTemp.Append("ST1:\n");
            strTemp.Append("ST2:\n");
            strTemp.AppendFormat("PID:{0}\n",dl.pid);
            strTemp.AppendFormat("SID:{0}\n",dl.sid);
            strTemp.AppendFormat("DID:{0}\n",dl.did);
            strTemp.AppendFormat("MGC:{0}\n",dl.md5hash);
            strTemp.Append("SPL:0\n");
            strTemp.Append("SPH:0\n\n\n");
            return strTemp.ToString();
        }

        public string CommReqSeek(dlInfo dl)
        {
            StringBuilder strTemp = new StringBuilder();

            strTemp.Append("GET / HTTP/1.0\n");
            strTemp.Append("Accept: */*\n");
            strTemp.Append("User-Agent: BMTPAgent\n");
            strTemp.Append("Host: VOD\n");
            strTemp.Append("UGT:NETPLAYER\n");
            strTemp.Append("WAY:1\n");
            strTemp.Append("MTD:3\n");
            strTemp.AppendFormat("SPL:{0}\n", dl.filepos & 0xffffffff);
            strTemp.AppendFormat("SPH:{0}\n\n\n", dl.filepos >> 32);
            return strTemp.ToString();
        }

        public string CommReqNop()
        {
            StringBuilder strTemp = new StringBuilder();

            strTemp.Append("GET / HTTP/1.0\n");
            strTemp.Append("Accept: */*\n");
            strTemp.Append("User-Agent: BMTPAgent\n");
            strTemp.Append("Host: VOD\n");
            strTemp.Append("UGT:NETPLAYER\n");
            strTemp.Append("WAY:1\n");
            strTemp.Append("MTD:2\n\n\n");
            return strTemp.ToString();
        }

        public string DataTranferReq(dlInfo dl)
        {
            StringBuilder strTemp = new StringBuilder();

            strTemp.Append("GET / HTTP/1.0\n");
            strTemp.Append("Accept: */*\n");
            strTemp.Append("User-Agent: BMTPAgent\n");
            strTemp.Append("Host: VOD\n");
            strTemp.Append("UGT:NETPLAYER\n");
            strTemp.Append("WAY:2\n");
            strTemp.Append("FPT:1\n");
            strTemp.AppendFormat("CID:{0}\n",dl.cid);
            strTemp.AppendFormat("USN:{0}\n",dl.usn);
            strTemp.AppendFormat("STT:{0}\n\n\n",dl.time);
            return strTemp.ToString();
        }
    }

    public class downloader
    {
        
        public enum status {st_Idle=0,st_Comm=1,st_Download=2,st_End=3,st_Err=32};
        public status Stat;
        public dlInfo Config;

        IPAddress vodhostip;
        private Socket CAgent;      //控制流
        private Socket DAgent;      //数据流

        public Boolean Err;
        public string ErrStr;

        public event ProcessDelegate OnDlding;
        public event ProcessDelegate OnDlded;
        public event ProcessDelegate OnErr;

        public downloader(dlInfo dl)
        {
            Config  = dl;
            Config.md5hash=new constructor().mgc_hash(dl.pid,dl.sid,dl.did,dl.pfg);
        }

        public downloader(uint pid, uint sid, uint did, uint pfg)
        {
            Config.pid = pid;
            Config.sid = sid;
            Config.did = did;
            Config.pfg = pfg;
            Config.md5hash=new constructor().mgc_hash(pid,sid,did,pfg);
        }

        public downloader(uint pid, uint sid, uint did, uint pfg,long pos)
        {
            Config.pid = pid;
            Config.sid = sid;
            Config.did = did;
            Config.pfg = pfg;
            Config.filepos = pos;
            Config.md5hash = new constructor().mgc_hash(pid, sid, did, pfg);
        }

        public void GetMediaInfo()
        {
            Err=false;
            ErrStr="";
            CommReqInit();
        }

        private void CommNop(object t)
        {
            if (Stat == status.st_Download)
                CommReqNop();
        }

        public void SaveToFile(string filepath)
        {
            //MemoryMappedFile f = MemoryMappedFile.CreateFromFile(filepath,FileMode.OpenOrCreate,Config.md5hash,Config.filelen);
            //MemoryMappedViewAccessor fw = f.CreateViewAccessor(Config.filepos & 0x7fffffff00000000, Config.filelen &(long) 0xffffffff);
            //MemoryMappedViewAccessor fw = f.CreateViewAccessor(0, Config.filelen);
            FileStream fw = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
            byte[] t = new byte[8];
            byte[] buffer = new byte[65536];
            TimerCallback tcb = new TimerCallback(CommNop);
            Timer timer = new Timer(tcb, null, 1000, 3000);
            fw.SetLength(Config.filepos);
            fw.Seek(0, SeekOrigin.End);
            CommReqSeek(Config.filepos);
            DataTransferReq();
            DAgent.Blocking = true;
            int i=0;
            while(i<48)
                i+=DAgent.Receive(t,1, SocketFlags.None);

            Stat=status.st_Download;

            while (Config.filepos < Config.filelen)
            {
                int ret=DAgent.Receive(buffer, 65536, SocketFlags.None);
                //fw.WriteArray(Config.filepos, buffer, 0, ret);
                fw.Write(buffer, 0, ret);
                Config.filepos +=(Int64)ret;
            }

            fw.Flush();
            fw.Dispose();
            //f.Dispose();
            timer.Change(0, int.MaxValue);
            timer.Dispose();
            fw = null;
            //f = null;
            timer = null;
            CAgent.Close();
            DAgent.Close();
            CAgent = null;
            DAgent = null;
            GC.Collect();
            Stat = status.st_End;
            OnDlded(this, EventArgs.Empty);
        }

        private void stub(object sender, EventArgs args)
        {
            return;
        }

        private void AddDelegate()
        {
            OnDlded+=new ProcessDelegate(stub);
            OnDlding+=new ProcessDelegate(stub);
            OnErr+=new ProcessDelegate(stub);
        }

        public void CommReqInit()
        {
            string req;
            byte[] byteTemp;
            MediaInfoParser parser;
            AddDelegate();
            Stat = status.st_Comm;
            req=new constructor().CommReqInfo(Config);
            byteTemp=Encoding.ASCII.GetBytes(req);
            vodhostip = Dns.GetHostAddresses("vod.hebut.edu.cn")[0];
            CAgent=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                CAgent.Connect(vodhostip, 1680);
            }
            catch(SocketException err)
            {
                Err=true;
                ErrStr=err.Message.ToString();
                OnErr(this, EventArgs.Empty);
                return;
            }

            CAgent.Send(byteTemp);

            try
            {
                CAgent.Receive(byteTemp);
            }
            catch(Exception err)
            {
                Err=true;
                ErrStr=err.Message.ToString();
                OnErr(this, EventArgs.Empty);
                return;
            }
            
            

            parser=new MediaInfoParser(byteTemp);
            if(parser.Err)
            {
                Err=true;
                ErrStr=parser.ErrStr;
                OnErr(this, EventArgs.Empty);
                return;
            }
            Config.cid=parser.cid;
            Config.fileext=parser.fileext;
            Config.filelen=parser.filelen;
            Config.time=parser.time;
            Config.usn=parser.usn;
            Config.videoname=parser.videoname;
        }

        public void CommReqSeek(Int64 pos)
        {
            string req;
            byte[] byteTemp;
            Config.filepos = pos;
            req = new constructor().CommReqSeek(Config);
            byteTemp = Encoding.ASCII.GetBytes(req);
            try
            {
                CAgent.Send(byteTemp);
            }
            catch(Exception err)
            {
                Err=true;
                ErrStr="定位时发生错误!";
                OnErr(this, EventArgs.Empty);
                return;
            }
            CAgent.Receive(byteTemp);
        }

        public void CommReqNop()
        {
            string req;
            byte[] byteTemp;
            req = new constructor().CommReqNop();
            byteTemp = Encoding.ASCII.GetBytes(req);
            try
            {
                CAgent.Send(byteTemp);
            }
            catch(Exception err)
            {
                Err=true;
                ErrStr="NOP数据发送时出错!";
                OnErr(this, EventArgs.Empty);
                return;
            }
            CAgent.Receive(byteTemp);
            OnDlding(this, EventArgs.Empty);
        }

        public void DataTransferReq()
        {
            string req;
            byte[] byteTemp;

            req=new constructor().DataTranferReq(Config);
            byteTemp = Encoding.ASCII.GetBytes(req);

            DAgent=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                DAgent.Connect(vodhostip, 1680);
            }
            catch (SocketException err)
            {
                Err = true;
                ErrStr = err.Message.ToString();
                OnErr(this, EventArgs.Empty);
                return;
            }

            DAgent.Send(byteTemp);

        }
    }
}
